import os.path
import re
from typing import Union, Callable, List, Dict

from scripts.Log import Log
from scripts.util.ProgressHelper import ProgressHelper
from scripts.util.Util import Util
from scripts.actions.Action import Action
from scripts.actions.Context import Context


class BackupMiscAction(Action):
    KEY = Action.register('user-files')
    DISPLAY_NAME = 'User files'

    PATTERN_BACKUP_PATH = re.compile(r'/storage/emulated/(\d+)/(.+)')

    def __init__(self, context: Context):
        super().__init__(BackupMiscAction.KEY, BackupMiscAction.DISPLAY_NAME, context)

    def _on_backup(self, progress: Union[None, Callable[[int, int, str], None]]):
        device = self.wait_for_boot_up_ready()
        with device.shell() as shell:
            user_infos = Util.dump_pkg_infos(shell)
            user_list = user_infos.keys()

            # start user
            Action.start_users(user_list, shell=shell)
            Action.wait_for_filesystem_ready(user_list, shell=shell)

            # make backup list
            backup_file_list = shell.find('/storage/emulated/*', 1, 1)
            backup_list = []
            for backup_file in backup_file_list:
                matched = BackupMiscAction.PATTERN_BACKUP_PATH.fullmatch(backup_file)
                if matched:
                    user = matched.group(1)
                    name = matched.group(2)
                    if name in {'Android', 'devinfo.html'}:
                        continue  # ignore

                    backup_list.append(BackupMiscAction.BackupItem(backup_file,
                                                                   os.path.join(self.data_dir(), user, name)))

            # do backup
            prog = ProgressHelper(progress, len(backup_list))
            for item in backup_list:
                prog.on_message(item.device_path).inc()
                Action.pull_file(device, item, shell)
                Log.d('backup: %s' % item.device_path)

            prog.on_message('Done [%s]' % self.duration_time_str())
            Log.i('Backup success for %d misc' % len(backup_list))

    def _on_restore(self, progress: Union[None, Callable[[int, int, str], None]]):
        device = self.wait_for_boot_up_ready()
        with device.shell() as shell:
            user_infos = Util.dump_pkg_infos(shell)
            user_list = user_infos.keys()
            file_exists_helper = Util.FileExistsHelper(shell)

            # start user
            Action.start_users(user_list, shell=shell)
            Action.wait_for_filesystem_ready(user_list, shell=shell)

            # make restore list
            restore_dict: Dict[int, List[BackupMiscAction.RestoreItem]] = dict()
            user_dirs = os.listdir(self.data_dir())
            for user_dir in user_dirs:
                if not user_dir.isnumeric():
                    continue

                user = int(user_dir)
                host_dir = os.path.join(self.data_dir(), user_dir)
                device_dir = '/storage/emulated/%s' % user_dir
                if not file_exists_helper.exists(device_dir):
                    Log.w('ignore not accessible path under: %s' % device_dir)
                    continue  # ignore not accessible paths

                restore_dict[user] = []
                misc_dirs = os.listdir(host_dir)
                for misc_dir in misc_dirs:
                    restore_dict[user].append(BackupMiscAction.RestoreItem(
                        '%s/%s' % (device_dir, misc_dir),
                        os.path.join(host_dir, misc_dir)
                    ))

            # count items
            count = 0
            for item_list in restore_dict.values():
                count += len(item_list)
            prog = ProgressHelper(progress, count)

            # do restore
            for user in restore_dict:
                for item in restore_dict[user]:
                    prog.on_message(item.device_path).inc()
                    Action.push_file(device, item, shell)
                    Log.d('restore: %s' % item.device_path)

            prog.on_message('Done [%s]' % self.duration_time_str())

            Log.i('Restore success for %d misc' % count)

    class BackupItem(Action.FileTransferItem):
        def __init__(self, device_path: str, host_path: str):
            super().__init__(device_path, host_path, compress=False)

    class RestoreItem(Action.FileTransferItem):
        def __init__(self, device_path: str, host_path: str):
            super().__init__(device_path, host_path, compress=False)
